package Game.Objects;

import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Random;

import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;

import GUI.GuiManager;
import Game.GameHelper;
import Game.Buff.BuffManager;
import Game.Buff.DebuffHunger;
import Game.Items.GameItem;
import Game.Items.Inventory;
import Game.Items.Potions.GISmallHealthPotion;
import Game.Items.Weapons.GIBronzeSword;
import Game.Objects.Creatures.GOCreature;
import GameMath.GPoint;
import Tools.SoundManager;
import Tools.Lighting.LightPoint;

/** Player
 * @author Aikidos
 */
public class GOPlayer extends GameObject {

	/** Buff manager */
	private BuffManager _buffManager;
	
	/** Player inventory */
	private Inventory _inventory;

	/** Current health points */
	private int _health;
	
	/** Self light */
	private LightPoint _selfLight;

	/** Timer of control update */
	private int _timerControlUpdate = 0;
	
	/** Switch diagonal move (horizontal or vertical) */
	private boolean _diagonalMoveSwitch = false;
	
	/** Level */
	private int _level = 1;
	
	/** Current experience */
	private int _experience;
	
	/** Max experience for level up */
	private int _experienceMax = 100;
	
	/** Skill points */
	private int _skillPoints;
	
	/** Luck */
	private int _luck;
	
	/** Weight */
	private float _weight;
	
	/** Intelligence */
	private int _intelligence;
	
	/** Hunger */
	private int _hunger;
	
	/** Minimum damage */
	public int MinDamage;
	
	/** Maximum damage */
	public int MaxDamage;

	/** Defense */
	public int Defense;
	
	/** Block chance */
	public int BlockChance;
	
	/** Maximum health */
	public int MaxHealth;
	
	/** Maximum weight */
	public float MaxWeight;
	
	/** Magic */
	public int Magic;
	
	/** Gold */
	public int Gold;
	
	/** Open map range */
	public int OpenMapRange;

	public GOPlayer(ObjectManager objectManager) throws SlickException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException {
		super(objectManager, "Player");

		_isPlayer = true;

		// Load sprite
		setSprite(new Image("res/gfx/creatures/player.png"));

		// Initialize buff manager
		_buffManager = new BuffManager(this);
		
		// Initialize inventory
		_inventory = new Inventory(this);

		_health = MaxHealth = 25;
		OpenMapRange = 11;
		
		MaxWeight = 20f;
		
		// Set hunger
		_hunger = 0;
		
		// Set default damage
		MinDamage = 1;
		MaxDamage = 2;

		// Add default weapon
		GameItem defaultWeapon = _inventory.addItem(new GIBronzeSword());
		_inventory.useItem(defaultWeapon);
		
		// Add some default items
		_inventory.addItem(new GISmallHealthPotion());
	}

	/** Initialize self light */
	public void initSelfLight() {
		_selfLight = new LightPoint(_objectManager.getLevel().getLightManager());
		_selfLight.setRange(10);
		_selfLight.setPosition(getCoord());
		_objectManager.getLevel().getLightManager().addLight(_selfLight);
	}
	
	/** Add some health
	 * @param health - Health
	 */
	public void addHealth(int health) {
		_health += health;

		if (_health > MaxHealth)
			_health = MaxHealth;
	}

	/** Adds weight
	 * @param weight - Weight
	 */
	public void addWeight(float weight) {
		_weight += weight;
	}
	
	/** Sub some health
	 * @param health - Health
	 */
	public void subHealth(int health) {
		_health -= health;

		if (_health < 0)
			_health = 0;
	}

	/** Sub weight
	 * @param weight - Weight
	 */
	public void subWeight(float weight) {
		_weight -= weight;
		if (_weight < 0) _weight = 0;
	}
	
	/** Returns health
	 * @return Health
	 */
	public int getHealth() {
		return _health;
	}

	/** Returns hunger
	 * @return Hunger
	 */
	public int getHunger() {
		return _hunger;
	}
	
	/** Returns weight
	 * @return Weight
	 */
	public float getWeight() {
		return _weight;
	}
	
	/** Returns inventory
	 * @return Inventory
	 */
	public Inventory getInventory() {
		return _inventory;
	}
	
	/** Returns buff manager
	 * @return Buff manager
	 */
	public BuffManager getBuffManager() {
		return _buffManager;
	}
	
	/** Returns player self light
	 * @return Self light
	 */
	public LightPoint getSelfLight() {
		return _selfLight;
	}
	
	/** Returns current exp
	 * @return Current exp
	 */
	public int getExp() {
		return _experience;
	}
	
	/** Returns max experience
	 * @return Max experience for level up
	 */
 	public int getExpMax() {
		return _experienceMax;
	}
	
	/** Returns player's level
	 * @return Player's level;
	 */
	public int getLvL() {
		return _level;
	}
	
	/** Returns skill points
	 * @return Skill points
	 */
	public int getSkillPoints() {
		return _skillPoints;
	}
	
	/** Returns luck
	 * @return Luck
	 */
	public int getLuck() {
		return _luck;
	}
	
	/** Returns intelligence
	 * @return Intelligence
	 */
	public int getIntelligence() {
		return _intelligence;
	}
	
	/** Adds hunger
	 * @param hunger - Some hunger
	 */
	public void addHunger(int hunger) {
		_hunger += hunger;
		if (_hunger > 1000) _hunger = 1000;
	}
	
	/** Add exp
	 * @param exp - Experience
	 */
	public void addExp(int exp) {
		_experience += exp;
		
		while (_experience >= _experienceMax) {
			_experience -= _experienceMax;
			_level++;
			_skillPoints++;
			_experienceMax += ((_experienceMax / 100) * 25);
			
			// Show message
			GuiManager.getMessenger().addMessage("[green]You[/green] level up!");
			
			// Play sound
			SoundManager.play("sfx_level_up", 0.2f);
		}
	}
	
	/** Add luck
	 * @param luck - Luck
	 */
	public void addLuck(int luck) {
		_luck += luck;
		if (_luck > 999) _luck = 999;
	}
	
	/** Add intelligence
	 * @param intelligence - Intelligence
	 */
	public void addIntelligence(int intelligence) {
		_intelligence += intelligence;
	}
	
	/** Sub some hunger
	 * @param hunger - Some hunger
	 */
	public void subHunger(int hunger) {
		_hunger -= hunger;
		if (_hunger < 0)
			_hunger = 0;
	}
	
	/** Set health
	 * @param health - Health
	 */
	public void setHealth(int health) {
		_health = (health <= MaxHealth) ? ((health >= 0) ? health : 0) : MaxHealth;
	}
	
	/** Spend skill point */
	public void spendSkillPoint() {
		if (_skillPoints > 0) _skillPoints--;
	}
	
	/** Is died? */
	public boolean isDied() {
		return (_health <= 0);
	}

	/** Is overload? */
	public boolean isOverload() {
		return (_weight > MaxWeight);
	}
	
	/** Attack
	 * @param gameObject - Game object
	 */
	public void attack(GameObject gameObject) {
		Random rnd = GameHelper.getRandom();

		if (gameObject.isCreature()) {
			GOCreature creature = (GOCreature) gameObject;

			// Block?
			if (GameHelper.getRandom().nextInt(100) <= creature.BlockChance) {
				// Show message
				GuiManager.getMessenger().addMessage("[red]" + creature.getName() + "[/red] blocked attack!");
				return;
			}
			
			// Save enemy hp
			int oldEnemyHP = creature.getHealth();
			
			// Call attack method for each item
			for (GameItem item : getInventory().getItems()) {
				item.attack(creature);
			}
			
			// Calculate damage
			int d = MinDamage + rnd.nextInt((MaxDamage - MinDamage) + 1)
					- creature.Defense;

			// If damage > 0
			if (d > 0) {
				creature.subHealth(d);

				// Calculate showing damage
				int sDmg = oldEnemyHP - creature.getHealth();
				
				// Show attacks messages
				GuiManager.getMessenger().addMessage("[green]You[/green]"
						+ " attacked [red]" + creature.getName() + "[/red] (" + ((sDmg > 0) ? "-" : "+")
						+ String.valueOf(sDmg) + " hp)");

				if (creature.isDied()) {
					GuiManager.getMessenger().addMessage("[green]You[/green] killed "
							+ "[red]" + creature.getName() + "[/red]");
				}

				// If creature is died give all loot
				if (creature.isDied()) {
					List<GameItem> items = creature.getInventory().getItems();

					for (GameItem item : items) {
						if ((creature.getInventory().getDropChance(item) > 0) && 
								(GameHelper.getRandom().nextInt(1000 - _luck) <= creature.getInventory().getDropChance(item))) {
							item.off();
							getInventory().addItem(item);
							
							// Show pick up message
							GuiManager.getMessenger().addMessage("[green]You[/green]"
									+ " picked up a [yellow]" + item.getName()
									+ "[/yellow]");
						}
					}
					
					// Give creature magic
					Magic += creature.Magic + _intelligence;
					
					// Give creature experience
					addExp(creature.Experience + _intelligence);
					
					// Give creature gold
					if (creature.Gold > 0) {
						int creatureGold = rnd.nextInt(creature.Gold + 1);
						if (creatureGold > 0) {
							Gold += creatureGold;
							GuiManager.getMessenger().addMessage("[green]You[/green]"
									+ " picked up a [yellow]Gold[/yellow] x" + creatureGold);
						}
					}
				}
			} else {
				// Else show message missed
				GuiManager.getMessenger().addMessage("[green]You[/green]" + " missed [red]"
						+ creature.getName() + "[/red]");
			}
		}
	}

	@Override
	public void update(int delta) throws SlickException,
			InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException,
			NoSuchMethodException, SecurityException, ClassNotFoundException {
		// Update buff manager
		_buffManager.update(delta);
		
		// Get input system
		Input input = GameHelper.getGame().getInput();
		
		if (!GuiManager.isAnyOpen()) {
			// Wait
			if (input.isKeyPressed(Input.KEY_SPACE)) {
				if (isOverload()) {
					showOverloadMessage();
					return;
				}
				
				_objectManager.getLevel().step();
				return;
			}

			updateControl(delta);

			// Use
			if (input.isKeyPressed(Input.KEY_E)) {
				// Get objects
				GameObject[] gameObjects = _objectManager
						.getObjectsByCoord(_objectManager.getObjectCoord(this));
				for (GameObject gameObject : gameObjects) {
					if (gameObject.isCanBeUsed()) {
						gameObject.use(this);
					}
				}
			}
		}
	}

	@Override
	public void step() throws SlickException {
		// Move self light to player
		_selfLight.setPosition(getCoord());
		
		// Step buff
		_buffManager.step();
		
		// Add hunger
		if (getHunger() < 1000)
			addHunger(1);
		
		if (getHunger() >= 850) {
			if (getBuffManager().getBuffByClass("Game.Buff.DebuffHunger") == null)
				getBuffManager().addBuff(new DebuffHunger(getBuffManager()));
		}
	}

	@Override
	public boolean onCollision(GameObject other, boolean moveOn) {
		// Attack enemy
		if (other.isCreature()) {
			GOCreature creature = (GOCreature) other;
			if (creature.Type == EGameObjectType.Enemy) {
				if (moveOn)
					attack(other);
				return false;
			}
		}

		return true;
	}


	/** Update control
	 * @param delta - Time delta
	 * @throws SlickException 
	 */
	private void updateControl(int delta) throws SlickException {
		// Create move vector
		GPoint moveVector = new GPoint();
		Input input = GameHelper.getGame().getInput();
		if (input.isKeyDown(Input.KEY_W))
			moveVector.y = -1;
		else if (moveVector.y == -1)
			moveVector.y = 0;
		if (input.isKeyDown(Input.KEY_A))
			moveVector.x = -1;
		else if (moveVector.x == -1)
			moveVector.x = 0;
		if (input.isKeyDown(Input.KEY_S))
			moveVector.y = 1;
		else if (moveVector.y == 1)
			moveVector.y = 0;
		if (input.isKeyDown(Input.KEY_D))
			moveVector.x = 1;
		else if (moveVector.x == 1)
			moveVector.x = 0;
		
		// Diagonal movement
		if ((moveVector.x != 0) && (moveVector.y != 0)) {
			if (!_diagonalMoveSwitch)
				moveVector.x = 0;
			else
				moveVector.y = 0;
			
			_diagonalMoveSwitch = !_diagonalMoveSwitch;
		}
		
		// If move vector is zero that reset timer
		if ((moveVector.x == 0) && (moveVector.y == 0)) {
			_timerControlUpdate = 0;
		}
		
		if (_timerControlUpdate == 0) {
			if ((moveVector.x != 0) || (moveVector.y != 0)) {
				_timerControlUpdate = 180;
				
				// Is overload?
				if (isOverload()) {
					showOverloadMessage();
					return;
				}
				
				// Move
				GPoint playerMapPos = _objectManager.getObjectCoord(this);
				GPoint newPlayerMapPos = new GPoint();
				newPlayerMapPos.set(playerMapPos);
				newPlayerMapPos.add(moveVector);
				
				_objectManager.moveTo(this, newPlayerMapPos);
				_objectManager.getLevel().step();
			}
		} else
			_timerControlUpdate = ((_timerControlUpdate - delta) < 0) ? 0
					: _timerControlUpdate - delta;
	}

	/** Shows overload message */
	private void showOverloadMessage() {
		GuiManager.getMessenger().addMessage("[green]You[/green] is overloaded!");
	}
	
}
